package mutation;

import java.lang.reflect.Array;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import ga.*;
import operations.*;
import util.DebugConfig;
import util.GAConfig;
import util.Log;
import model.*;
public class RandomChangeInputValue extends MutationStrategy {

	@Override
	public Individual mutate(Individual individual) {
		if(DebugConfig.ChangeInputValueClass){
			System.out.println("------- Mutation Change Value Strategy ---------------"); 
			System.out.println("--------------------individual1----------------------");
			System.out.println(individual.toString()+ " size:"+ individual.getSize());
		}
		long totalMutation=this.gaInfo.getTotalMutationChange();

		Individual mutatedIndividual=individual.clone();
		

		// Change Initial Constructions
		try {
			List<Operation> cons = mutatedIndividual.getInitialConstructors();
			for (int i = 0; i < cons.size(); i++) {
				Operation mutateOp=null;
		
				Operation op = cons.get(i);
	//		 System.out.println(i+ " RandomlyChangeInputValueClass: "+ op.toString());

				 if (!getMutationFlag(cons.size()))
					continue;

				if (op instanceof CreateObject) {
					 mutateOp = mutantConstructoParams(
							(CreateObject) op, mutatedIndividual);

				} else if (op instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant(
							(AssignConstant) op, mutatedIndividual);
					}
				if(mutateOp==null) continue;
				totalMutation++;

				cons.set(i, mutateOp);

			}

			List<Operation> ops = mutatedIndividual.getStatements();
			for (int i = 0; i < ops.size(); i++) {
				Operation op = ops.get(i);
//				 System.out.println(i+ " RandomlyChangeInputValueClass: "+ op.toString());

				Operation mutateOp=null;
				if (!getMutationFlag(ops.size()))
					continue;
				if(ops.get(i) instanceof CreateArray)
					 mutateOp = mutantArray((CreateArray) op,
								mutatedIndividual);

				if (ops.get(i) instanceof InvokeMethod) {
					 mutateOp = mutantMethodParams((InvokeMethod) op,
							mutatedIndividual);
				} else if (ops.get(i) instanceof CreateObject) {
					 mutateOp = mutantConstructoParams(
							(CreateObject) op, mutatedIndividual);

				} else if (ops.get(i) instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant(
							(AssignConstant) op, mutatedIndividual);
				}
				if(mutateOp==null) continue;
				totalMutation++;

				ops.set(i, mutateOp);

			}
		} catch (Throwable e){
			System.err.println("Change Input Value Class:" + e);
		}	
		if(DebugConfig.ChangeInputValueClass){
			System.out.println("--------------------individual1----------------------");
			System.out.println(individual.toString()+ " size:"+ individual.getSize());
			System.out.println("--------------------mutatedIndividual----------------------");
			System.out.println(mutatedIndividual.toString()+ " size:"+ mutatedIndividual.getSize());
		}
		

		this.gaInfo.setTotalMutationChange(totalMutation);

		return mutatedIndividual;
	}
	private Operation mutantArray(CreateArray op,Individual individual) throws Throwable {
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		 CreateArray array=((CreateArray)op);
		   Object theArray = Array.newInstance(array.getElementType(), array.getLength());
		   List<Object> inputList=array.getInputVariables();
		    for (int i = 0; i < array.getLength(); i++)
		    	if(Filter.isPrimitiveOrStringType(array.getElementType()))
		    		inputList.set(i, ValuesGenerator.modifyPrimitiveValue(array.getElementType(),inputList.get(i)));
		    	else
		    		inputList.set(i, Operation.generateReference(array.getElementType(),TestCluster));

		return op;
	}
			private Operation mutantMethodParams(InvokeMethod op,Individual individual) throws Throwable {
//				System.out.println("mutantMethodParams " + op.toString());
//				if(((InvokeMethod) op).getMethod().toString().contains("int reflectionCompare(java.lang.Object,java.lang.Object,java.util.Collection)"))
//					System.out.println("mutantMethodParams " + op.toString()+ " "+ ((InvokeMethod) op).getMethod());
					
				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, String[]> refFactory = TestCluster.getRefFactory();
				List<Class<?>>replacementObjectParam= op.getReplacementObjectParam();
				StatementKind m = op.getMethod();
				List<Class<?>> paramsTpe = m.getInputTypes();
				List<Object> oldValues=op.getValues();
				List<Object> newValues=new LinkedList<Object>();

				//Change return reference
					String _return = (((MethodDeclared) m).isVoid()) ? null : Operation.getReference(((MethodDeclared) m).getMethod().getReturnType().getName(), TestCluster.getRefFactory());
					if(_return==null&&!((MethodDeclared) m).isVoid()){
						Class<?> rType=Operation.generateReference(((MethodDeclared) m).getMethod().getReturnType(), TestCluster);
						if(rType!=null){
							if(!rType.getName().contains("java."))
							if(!rType.getName().startsWith("sun"))
							if(!rType.getName().contains("$")) 
							if(!Filter.isPrimitiveOrStringType(rType)){
											_return=Operation.getReference(rType.getName(), refFactory);//clz2.toString();	
								}
						}
					}
					op.setReturn(_return);
				//Cahneg parameters
				boolean commitedChange=false;
				for(int i = 0; i < paramsTpe.size(); i++) {
				      Class<?> paramTpe = paramsTpe.get(i);
						if (Filter.isPrimitiveOrStringType(paramTpe))
							newValues.add(ValuesGenerator.modifyPrimitiveValue(paramTpe,oldValues.get(i)));
						else{
							if(Filter.isObjectType(paramTpe)){
								paramTpe=Operation.generateReference(paramTpe, TestCluster);
								if (Filter.isPrimitiveOrStringType(paramTpe))
									newValues.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
								else{
									if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(paramTpe==null))
										newValues.add(null);
									else
										newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
								}
							}
							else{
								if(paramTpe.equals(java.lang.Class.class)){
									List<Class<?>> genericsTpe=m.getGenericInputRetrnTypes();
									Class<?> classRamd=genericsTpe.get(ValuesGenerator.getRandomIntBetween(0,genericsTpe.size()-1));
									paramTpe=Operation.generateReference(classRamd, TestCluster);
									if (Filter.isPrimitiveOrStringType(paramTpe))
										newValues.add(ValuesGenerator.generatePrimitiveValue(paramTpe));//values.add(paramTpe);
									else{
										if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(classRamd==null))
											newValues.add(null);
										else{
											if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(paramTpe==null))
												newValues.add(null);
											else
												newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
										}
									}
								}
								else{
								paramTpe=Operation.generateReference(paramTpe, TestCluster);
								if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(paramTpe==null))
									newValues.add(null);
								else
									newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
								}
							}
						}					
						replacementObjectParam.set(i,paramTpe);
						commitedChange=true;
			   }
				if(commitedChange)
					op.setValues(newValues);
//				System.out.println("mutantMethodParams " + op.toString());
				return op;
			}
			private Operation mutantConstructoParams(CreateObject op,Individual individual) throws Throwable {
//				System.out.println("random.mutantMethodParams " + op.toString());

				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, String[]> refFactory = TestCluster.getRefFactory();
				StatementKind cons = op.getConstructor();
				List<Class<?>> paramsTpe = cons.getInputTypes();
				List<Object> oldValues=op.getValues();
				List<Object> newValues=new LinkedList<Object>();

				boolean commitedChange=false;
				for(int i = 0; i < paramsTpe.size(); i++) {
					Class<?> paramTpe=paramsTpe.get(i);
					if (Filter.isPrimitiveOrStringType(paramTpe))
						newValues.add(ValuesGenerator.modifyPrimitiveValue(paramTpe,oldValues.get(i)));
					else{
						if(Filter.isObjectType(paramTpe)){
							paramTpe=Operation.generateReference(paramTpe, TestCluster);
							if (Filter.isPrimitiveOrStringType(paramTpe))
								newValues.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
							else{
								if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
									newValues.add(null);
								else
									newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
							}
						}
						else{
							paramTpe=Operation.generateReference(paramTpe, TestCluster);
							if(paramTpe==null)
								return op;
							if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
								newValues.add(null);
							else
								newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
						}
					}
					commitedChange=true;
			}
				if(commitedChange){
					op.setValues(newValues);
					op.addInfo(new OperationResult());
				}

				return op;
			}
			private Operation mutantAssignConstant(AssignConstant op,Individual individual) throws Throwable {
				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, CUT>  cluster= TestCluster.getCluster();
				String cName= ((AssignConstant) op).getDeclaringClass().getName();
				CUT cut =cluster.get(cName) ;
				String ref_=op.getRefrence();
				op = AssignConstant.generate(cut,TestCluster);
				return op;
			}
	@Override
	public boolean getMutationFlag(int size) {
		float mutationRate=1.0f/size;
		if(GAConfig.mutation_rate!=0f)
			mutationRate=GAConfig.mutation_rate;
		if(ValuesGenerator.getBoolean(mutationRate))
			return true;
		return false;

	}
}
